package com.jinanzhuan.sqlciphertest;

import android.util.Base64;
import android.util.Log;

import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import static android.util.Base64.decode;

/**
 * 1、使用 12 字节的初始向量，并绝不在用同一密钥的时候复用初始向量（记得使用像 SecureRandom 这样的在密码学意义上安全的伪随机数生成器）
 * 2、使用 128 位长的认证标签
 * 3、使用 128 位的密钥长度（够长了！）
 * 4、把初始向量、密文还有认证标签打包成一条消息
 */
public class AndroidEncryptUtils {
    private Cipher cipher;

    public void initGCM() {
        SecretKey key = getSecretKey();
        byte[] iv = getGcmIv();
        initGCM(key, iv);
    }

    private SecretKey getSecretKey() {
        String key = "easemobchatdbkey";
        byte[] keyByte = new byte[16];
        for(int i = 0; i < keyByte.length; i++) {
            byte charAt = (byte) key.charAt(i);
            keyByte[i] = charAt;
        }
        Log.e("TAG", "secreteKey: "+ Arrays.toString(keyByte));

        return new SecretKeySpec(keyByte, "AES");
    }

    public byte[] getGcmIv() {
        SecureRandom secureRandom = new SecureRandom();
        byte[] iv = new byte[12]; // 使用同一密钥时初始向量绝不能重用
        secureRandom.nextBytes(iv);
        Log.e("TAG", "iv: "+Arrays.toString(iv));
        return iv;
    }

    public void initGCM(Key key, byte[] iv) {
        try {
            cipher = Cipher.getInstance("AES/GCM/NoPadding");
            //GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] ivInit = cipher.getIV();
            Log.e("TAG", "initGCM: "+Arrays.toString(ivInit));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
    }


    public String aesEncrypt(String plainMsg, int b64Flag) {
        try {
            byte[] stringBytes = plainMsg.getBytes("UTF-8");
            byte[] encryptedBytes = cipher.doFinal(stringBytes);
            byte[] iv = cipher.getIV();
            Log.e("TAG", "aesEncrypt: iv: "+Arrays.toString(iv));
            ByteBuffer byteBuffer = ByteBuffer.allocate(1 + iv.length + encryptedBytes.length);
            byteBuffer.put((byte) iv.length);
            byteBuffer.put(iv);
            byteBuffer.put(encryptedBytes);
            return b64Encode(byteBuffer.array(), b64Flag);
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("TAG", Arrays.toString(e.getStackTrace()));
        }
        return null;
    }

    public String aesDecrypt(String encryptedMsg, Key key, int b64Flag) {
        try {
            Log.e("TAG", "decrypt key : "+ key.getAlgorithm() + " class: " + key);
            byte[] b64Out = b64Decode(encryptedMsg, b64Flag);
            ByteBuffer byteBuffer = ByteBuffer.wrap(b64Out);
            int ivLength = byteBuffer.get();
            byte[] iv = new byte[ivLength];
            byteBuffer.get(iv);

            Log.e("TAG", "decrypt iv : "+ Arrays.toString(iv));
            byte[] cipherText = new byte[byteBuffer.remaining()];
            byteBuffer.get(cipherText);

            GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv);
            Cipher decipher = Cipher.getInstance("AES/GCM/NoPadding");
            decipher.init(Cipher.DECRYPT_MODE, key, parameterSpec);
            byte[] decryptedBytes = decipher.doFinal(cipherText);

            return new String(decryptedBytes);
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("TAG", Arrays.toString(e.getStackTrace()));
        }
        return null;
    }

    public static String b64Encode(byte[] data, int b64Flag) {
        return Base64.encodeToString(data, 0, data.length, b64Flag == B64_ENCODE_FLAG.ONESDK_B64_NO_WRAP.ordinal() ? Base64.NO_WRAP : Base64.DEFAULT);
    }

    public static byte[] b64Decode(String encryptedMsg) {
        return decode(encryptedMsg, Base64.DEFAULT);
    }

    public static byte[] b64Decode(String encryptedMsg, int b64Flag) {
        return decode(encryptedMsg, b64Flag == B64_ENCODE_FLAG.ONESDK_B64_NO_WRAP.ordinal() ? Base64.NO_WRAP : Base64.DEFAULT);
    }

    enum B64_ENCODE_FLAG {
        ONESDK_B64_DEFAULT,
        ONESDK_B64_NO_WRAP
    }
}

